<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<script src="../../../resources/js-test.js"></script>
</head>
<body>
<p id=description></p>
<div id="console"></div>
<script>

window.jsTestIsAsync = true;
var mutations;
var mutations2;
var mutationsWithOldValue;
var calls;
var charDataNode;

function testBasic() {
    var div;
    var observer;

    function start() {
        debug('Testing basic aspects of characterData observation.');

        mutations = null;
        div = document.createElement('div');
        div.textContent = 'foo';
        charDataNode = div.firstChild;
        observer = new MutationObserver(function(m) {
            mutations = m;
        });

        observer.observe(charDataNode, {characterData: true});
        charDataNode.textContent = 'bar';
        setTimeout(checkDisconnectAndMutate, 0);
    }

    function checkDisconnectAndMutate() {
        debug('...can characterData changes be observed at all');

        shouldBe('mutations.length', '1');
        shouldBe('mutations[0].type', '"characterData"');
        shouldBe('mutations[0].target', 'charDataNode');

        mutations = null;
        observer.disconnect();
        charDataNode.textContent = 'baz';
        setTimeout(checkNotDeliveredAndMutateMultiple, 0);
    }

    function checkNotDeliveredAndMutateMultiple() {
        debug('...observer.disconnect() should prevent further delivery of mutations.');

        shouldBe('mutations', 'null');
        charDataNode = document.createComment('');
        observer.observe(charDataNode, { characterData: true });
        charDataNode.textContent = 'foo';
        charDataNode.textContent = 'bar';
        setTimeout(finish);
    }

    function finish() {
        debug('...re-observing after disconnect works with the same observer.');

        shouldBe('mutations.length', '2');
        shouldBe('mutations[0].type', '"characterData"');
        shouldBe('mutations[0].target', 'charDataNode');
        shouldBe('mutations[1].type', '"characterData"');
        shouldBe('mutations[1].target', 'charDataNode');
        observer.disconnect();
        debug('');
        runNextTest();
    }

    start();
}

function testWrongType() {
    var div;
    var observer;

    function start() {
        debug('Testing that observing without specifying "characterData" does not result in hearing about characterData changes.');

        mutations = null;
        div = document.createElement('div');
        div.textContent = 'hello';
        charDataNode = div.firstChild;
        observer = new MutationObserver(function(m) {
            mutations = m;
        });

        observer.observe(charDataNode, {childList: true, attributes: true});
        charDataNode = 'goodbye';
        setTimeout(finish, 0);
    }

    function finish() {
        shouldBe('mutations', 'null');
        observer.disconnect();
        debug('');
        runNextTest();
    }

    start();
}

function testMultipleObservers() {
    var div;
    var observer;
    var observer2;

    function start() {
        debug('Testing that multiple observers can be registered to a given node and both receive mutations.');
        mutations = null;
        div = document.createElement('div');
        div.textContent = 'foo';
        charDataNode = div.firstChild;
        observer = new MutationObserver(function(m) {
            mutations = m;
        });
        observer2 = new MutationObserver(function(m) {
            mutations2 = m;
        });
        observer.observe(charDataNode, {characterData: true});
        observer2.observe(charDataNode, {characterData: true});
        charDataNode.textContent = 'bar';
        setTimeout(finish, 0);
    }

    function finish() {
        shouldBe('mutations.length', '1');
        shouldBe('mutations[0].type', '"characterData"');
        shouldBe('mutations[0].target', 'charDataNode');
        shouldBe('mutations2.length', '1');
        shouldBe('mutations2[0].type', '"characterData"');
        shouldBe('mutations2[0].target', 'charDataNode');
        observer.disconnect();
        observer2.disconnect();
        debug('');
        runNextTest();
    }

    start();
}

function testOrderingWrtDOMSubtreeModified() {
    var div, div2, subDiv;
    var observer;
    var listener;

    function start() {
        debug('Testing mutation records are enqueued for characterData before DOMSubtreeModified is dispatched.');

        mutations = null;
        div = document.body.appendChild(document.createElement('div'));
        div2 = document.body.appendChild(document.createElement('div'));

        subDiv = div.appendChild(document.createElement('div'));
        subDiv.textContent = 'foo';
        charDataNode = subDiv.firstChild;

        observer = new MutationObserver(function(m) {
            mutations = m;
        });

        listener = function(e) {
            div2.setAttribute('baz', 'bat');
        }

        div.addEventListener('DOMSubtreeModified', listener);
        observer.observe(charDataNode, {characterData: true});
        observer.observe(div2, {attributes: true});

        charDataNode.textContent = 'bar';

        setTimeout(finish, 0);
    }

    function finish() {
        shouldBe('mutations.length', '2');
        shouldBe('mutations[0].type', '"characterData"');
        shouldBe('mutations[1].type', '"attributes"');
        div.removeEventListener('DOMSubtreeModified', listener);
        document.body.removeChild(div);
        observer.disconnect();
        debug('');
        runNextTest();
    }

    start();
}

function testOldValue() {
    var div;
    var observer;

    function start() {
        debug('Testing that oldValue is returned when requested.');
        mutations = null;
        div = document.createElement('div');
        div.textContent = 'foo';
        charDataNode = div.firstChild;
        observer = new MutationObserver(function(mutations) {
            window.mutations = mutations;
        });
        observer.observe(charDataNode, {characterData: true, characterDataOldValue: true});
        charDataNode.textContent = 'bar';
        charDataNode.textContent = 'baz';
        setTimeout(finish, 0);
    }

    function finish() {
        shouldBe('mutations.length', '2');
        shouldBe('mutations[0].type', '"characterData"');
        shouldBe('mutations[0].target', 'charDataNode');
        shouldBe('mutations[0].oldValue', '"foo"');
        shouldBe('mutations[1].type', '"characterData"');
        shouldBe('mutations[1].target', 'charDataNode');
        shouldBe('mutations[1].oldValue', '"bar"');
        observer.disconnect();
        debug('');
        runNextTest();
    }

    start();
}

function testOldValueAsRequested() {
    var div;
    var observerWithOldValue;
    var observer;

    function start() {
        debug('Testing that oldValue is delivered as requested (or not).');
        mutations = null;
        mutationsWithOldValue = null;
        div = document.createElement('div');
        div.textContent = 'foo';
        charDataNode = div.firstChild;
        observerWithOldValue = new MutationObserver(function(mutations) {
            window.mutationsWithOldValue = mutations;
        });
        observer = new MutationObserver(function(mutations) {
            window.mutations = mutations;
        });
        observerWithOldValue.observe(charDataNode, {characterData: true, characterDataOldValue: true});
        observer.observe(charDataNode, {characterData: true});
        charDataNode.textContent = 'bar';
        setTimeout(finish, 0);
    }

    function finish() {
        shouldBe('mutationsWithOldValue.length', '1');
        shouldBe('mutationsWithOldValue[0].type', '"characterData"');
        shouldBe('mutationsWithOldValue[0].oldValue', '"foo"');
        shouldBe('mutations.length', '1');
        shouldBe('mutations[0].type', '"characterData"');
        shouldBe('mutations[0].oldValue', 'null');
        observerWithOldValue.disconnect();
        observer.disconnect();
        debug('');
        runNextTest();
    }

    start();
}

function testOldValueUnionMultipleObservations() {
    var div;
    var observer;

    function start() {
        debug('An observer with multiple observations will get characterDataOldValue if any entries request it.');
        mutations = null;
        div = document.createElement('div');
        div.textContent = 'foo';
        charDataNode = div.firstChild;
        observer = new MutationObserver(function(mutations) {
            window.mutations = mutations;
        });
        observer.observe(div, {characterData: true, characterDataOldValue: true, subtree: true});
        observer.observe(charDataNode, {characterData: true});
        charDataNode.textContent = 'bar';
        setTimeout(finish, 0);
    }

    function finish() {
        shouldBe('mutations.length', '1');
        shouldBe('mutations[0].type', '"characterData"');
        shouldBe('mutations[0].oldValue', '"foo"');
        observer.disconnect();
        debug('');
        runNextTest();
    }

    start();
}

var tests = [
    testBasic,
    testWrongType,
    testMultipleObservers,
    testOrderingWrtDOMSubtreeModified,
    testOldValue,
    testOldValueAsRequested,
    testOldValueUnionMultipleObservations
];
var testIndex = 0;

function runNextTest() {
    if (testIndex < tests.length)
        tests[testIndex++]();
    else
        finishJSTest();
}

description('Test WebKitMutationObserver.observe on CharacterData nodes');

runNextTest();
</script>
</body>
</html>
